/*
 * BABaseCommand.java
 * classes : com.qim.basdk.cmd.BABaseCommand
 * @author xuwei
 * V 1.0.0
 * Created at 2015年8月19日 下午3:47:38
 */
package com.langsheng.lsintell.cmd;

import android.text.TextUtils;

import com.langsheng.lsintell.cmd.data.LSCheckData;
import com.langsheng.lsintell.cmd.data.LSCmdHead;
import com.langsheng.lsintell.cmd.data.LSDataBuffer;
import com.langsheng.lsintell.utils.LSConst;
import com.langsheng.lsintell.utils.LSLog;
import com.langsheng.lsintell.utils.LSStringUtil;
import com.langsheng.lsintell.utils.encrypt.LSAES256;
import com.langsheng.lsintell.utils.encrypt.LSEncryptUtil;
import com.langsheng.lsintell.utils.encrypt.LSSM4;

import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Random;

/**
 * com.qim.basdk.cmd.LSBaseCommand
 * <p>
 * 基础命令结构,实现数据包打包,解包功能
 *
 * @author xuwei
 *         <p>
 *         created at 2015年8月19日 下午3:47:38
 */
public class LSBaseCommand extends LSCheckData {

    public final static String CONTENT_TYPE_BTF = "Text/Btf";
    public final static String CONTENT_TYPE_TEXT = "Text/Text";
    public final static String CONTENT_TYPE_URL = "Text/Url";
    public final static String CONTENT_TYPE_HTML = "Text/Html";

    public final static String TAG = "LSBaseCommand";
    /**
     * 数据包头信息
     */
    private LSCmdHead cmdHead = new LSCmdHead();
    /**
     * 数据包参数信息
     */
    private List<String> cmdParams = new ArrayList<String>();
    /**
     * 数据包属性信息
     */
    private Map<String, String> cmdProps = new HashMap<String, String>();
    /**
     * 数据包body信息
     */
    private LSDataBuffer contentData = new LSDataBuffer();

    /**
     * 设置命令名
     *
     * @param cmdCode
     */
    public void setCmdCode(int cmdCode) {
        cmdHead.setCmdCode((short) cmdCode);
    }

    /**
     * 获取指令名
     *
     * @return
     */
    public short getCmdCode() {
        return cmdHead.getCmdCode();
    }

    /**
     * 设置指令序号
     *
     * @param order
     */
    public void setCmdOrder(int order) {
        cmdHead.setCmdOrder(order);
    }

    /**
     * 获取指令序号
     *
     * @return
     */
    public int getCmdOrder() {
        return cmdHead.getCmdOrder();
    }

    /**
     * 获取指令数据包头
     *
     * @return
     */
    public LSCmdHead getHead() {
        return cmdHead;
    }

    /**
     * 根据位置获取数据包参数值
     *
     * @param index
     * @return
     */
    public String getParam(int index) {
        if (index >= cmdParams.size()) {
            return null;
        }
        return LSStringUtil.DeCodeString(cmdParams.get(index));
    }

    /**
     * 根据位置获取数据包参数值
     *
     * @param index
     * @return
     */
    public int getParamToInt(int index) {
        if (index >= cmdParams.size()) {
            return 0;
        }
        String value = cmdParams.get(index);
        if (TextUtils.isDigitsOnly(value)) {
            return Integer.valueOf(value);
        }
        return 0;
    }

    /**
     * 根据位置获取数据包参数值
     *
     * @param index
     * @return
     */
    public long getParamToLong(int index) {
        if (index >= cmdParams.size()) {
            return 0;
        }
        return Long.valueOf(cmdParams.get(index));
    }

    /**
     * 设置数据包参数值
     *
     * @param param
     */
    public void setParam(String param) {
        if (TextUtils.isEmpty(param)) {
            param = "";
        }
        cmdParams.add(LSStringUtil.EnCodeString(param));
    }

    /**
     * 设置数据包参数值
     *
     * @param param
     */
    public void setParam(int param) {
        setParam(String.valueOf(param));
    }

    /**
     * 获取数据包属性值
     *
     * @param key
     * @return 返回字符属性值
     */
    public String getProp(String key) {
        key = key.toLowerCase(Locale.getDefault());
        return LSStringUtil.DeCodeString(cmdProps.get(key));
    }

    /**
     * 获取数据包属性值
     *
     * @param key
     * @return 返回整型属性值
     */
    public int getPropToInt(String key) {
        key = key.toLowerCase(Locale.getDefault());
        String value = cmdProps.get(key);
        if (TextUtils.isEmpty(value) || !TextUtils.isDigitsOnly(value)) {
            return 0;
        }
        return Integer.valueOf(cmdProps.get(key));
    }

    /**
     * 获取数据包属性值
     *
     * @param key
     * @return 返回长整型属性值
     */
    public long getPropToLong(String key) {
        key = key.toLowerCase(Locale.getDefault());
        String value = cmdProps.get(key);
        if (TextUtils.isEmpty(value) || !TextUtils.isDigitsOnly(value)) {
            return 0;
        }
        return Long.valueOf(cmdProps.get(key));
    }

    /**
     * 获取所有属性
     *
     * @return
     */
    public Map<String, String> getAllProps() {
        return cmdProps;
    }

    /**
     * 设置数据包属性值
     *
     * @param key
     * @param value
     */
    public void setProp(String key, String value) {
        if (TextUtils.isEmpty(value)) {
            value = "";
        }
        key = key.toLowerCase(Locale.getDefault());
        cmdProps.put(key, LSStringUtil.EnCodeString(value));
    }

    /**
     * 设置数据包属性值
     *
     * @param key
     * @param value
     */
    public void setProp(String key, int value) {
        key = key.toLowerCase(Locale.getDefault());
        cmdProps.put(key, String.valueOf(value));
    }

    /**
     * 获取数据包body字符串信息
     *
     * @return
     */
    public String getContent() {
        return contentData.getDataString();
    }

    /**
     * 设置数据包body字符串信息
     *
     * @param content
     */
    public void setContent(String content) {
        if (TextUtils.isEmpty(content)) {
            return;
        }
        byte[] bContent = null;
        try {
            bContent = content.getBytes(LSConst.CHARSET_UTF8);
        } catch (UnsupportedEncodingException e) {
            bContent = content.getBytes();
        }
        if (!TextUtils.isEmpty(content)) {
            setProp(LSConst.CMD_PROP_CONTENT_LENGTH, bContent.length + "");
        }

        contentData.writeByteArray(bContent);
    }

    /**
     * 设置数据包body二进制字节信息
     *
     * @param content
     */
    public void setContent(byte[] content) {
        if (content != null) {
            setProp(LSConst.CMD_PROP_CONTENT_LENGTH, content.length + "");
        }

        contentData.writeByteArray(content);
    }

    /**
     * 获取数据包body流信息
     *
     * @return
     */
    public InputStream getContentStream() {
        return contentData.readBufferByStream();
    }

    /**
     * 获取数据包body数据字节数组
     *
     * @return
     */
    public byte[] getContentBytes() {
        return contentData.readBufferToByteArray();
    }

    /**
     * 解析数据包
     *
     * @param data
     */
    public void parseData(LSDataBuffer data) {
        if (!data.checkCommand()) {
            return;
        }

        // TODO:解析包头
        LSDataBuffer headBuffer = data.read(LSConst.CMD_HEAD_LENGTH);
        if (cmdHead == null) {
            cmdHead = new LSCmdHead();
        }
        cmdHead.parseHead(headBuffer);

        // ===============================
        // TODO:数据解密,排除文件服务器相关指令
        if (cmdHead.getEncryptType() == LSCmdHead.ENCRYPT_TYPE_AES256) {
            LSDataBuffer newData = new LSDataBuffer();
            byte[] key = LSEncryptUtil.getInstance().getEncryptKey(cmdHead.getEncryptKey1(), cmdHead.getEncryptKey2());
            try {
                newData.writeByteArray(LSAES256.encrypt(data.getBytes(), key, LSAES256.MODE_DECRYPT));
            } catch (Exception e) {
                e.printStackTrace();
            }
            data = newData;
        } else if (cmdHead.getEncryptType() == LSCmdHead.ENCRYPT_TYPE_SM4) {
            LSDataBuffer newData = new LSDataBuffer();
            byte[] key = LSEncryptUtil.getInstance().getEncryptKey(cmdHead.getEncryptKey1(), cmdHead.getEncryptKey2());
            try {
                newData.writeByteArray(LSSM4.SM4(data.getBytes(), key, LSSM4.MODE_DECRYPT));
            } catch (Exception e) {
                e.printStackTrace();
            }
            data = newData;
        }
        // ===============================

        try {
            // TODO:解析数据包中指令参数
            parseParam(data);

            // TODO:解析数据包中指令属性
            parsePropLine(data);

        } catch (Exception e) {
            // TODO:出现异常?or重新发送该指令?or?
            LSLog.e(TAG, "error cmd !!!\n" + toString());
            e.printStackTrace();
        }

        // TODO:解析数据包中body数据
        contentData = readContent(data);
        return;
    }

    /**
     * 解析出数据包body部分
     *
     * @param data
     * @return
     */
    private LSDataBuffer readContent(LSDataBuffer data) {
        int length = getPropToInt(LSConst.CMD_PROP_CONTENT_LENGTH);
        if (length == 0) {
            return new LSDataBuffer();
        }
        ;
        return data.read(length);
    }

    /**
     * 解析数据包属性信息
     *
     * @param buffer
     * @throws UnsupportedEncodingException
     */
    private void parsePropLine(LSDataBuffer buffer) throws UnsupportedEncodingException {
        byte[] data = readLine(buffer).getBytes();
        while (true) {

            // 如果读行获取到的数据为空行,则表示属性信息已解析完毕,后面的数据为body信息
            if (data == null || data.length == 0) {
                break;
            }

            String sParam = new String(data, LSConst.CHARSET_UTF8);
            String[] params = LSStringUtil.splitByFirstChar(sParam, LSConst.CMD_SPLIT_PROP);
            params[0] = params[0].toLowerCase(Locale.getDefault());
            cmdProps.put(params[0], params[1]);

            // 读取下一行数据
            data = readLine(buffer).getBytes();
        }
    }

    /**
     * 解析数据包中参数信息
     *
     * @param buffer
     * @throws UnsupportedEncodingException
     */
    private void parseParam(LSDataBuffer buffer) throws UnsupportedEncodingException {

        // 读取第一行数据,转成byte数组
        byte[] data = readLine(buffer).getBytes();
        // 讲字符数组转成utf8字符以便解析
        String sParam = new String(data, LSConst.CHARSET_UTF8);

        String[] params = sParam.split(LSConst.CMD_SPLIT_PARAM);

        for (String param : params) {
            cmdParams.add(param);
        }
    }

    /**
     * 从缓存数据中读取一行数据(不包括'\n'),
     * <p>
     * 然后将剩下的数据中,第一个'\n'字符跳过
     *
     * @param buffer
     * @return
     */
    private static LSDataBuffer readLine(LSDataBuffer buffer) {
        int index = findNextCF(buffer);
        // 读取一行数据,截止到'\n'前一个字符
        LSDataBuffer line = buffer.read(index);
        // 在缓存中去掉第一个'\n'字符
        buffer.skip(LSConst.LENGTH_BYTE);
        return line;
    }

    /**
     * 查找下一个换行符'\n'位置
     *
     * @return
     */
    private static int findNextCF(LSDataBuffer buffer) {
        return buffer.findNextChar(LSConst.CMD_SPLIT_LINE);
    }

    /**
     * 将数据包打包成字符串形式,包含数据包参数,属性,body
     *
     * @return
     */
    private String packageParamAndPropToString() {
        StringBuilder ret = new StringBuilder();
        for (String param : cmdParams) {
            ret.append(param);
            ret.append(LSConst.CMD_SPLIT_PARAM);
        }

        if (cmdParams.size() != 0) {
            ret.deleteCharAt(ret.length() - 1);
        }

        ret.append(LSConst.CMD_SPLIT_LINE_EX);

        for (String key : cmdProps.keySet()) {
            ret.append(key);
            ret.append(LSConst.CMD_SPLIT_PROP);
            ret.append(cmdProps.get(key));
            ret.append(LSConst.CMD_SPLIT_LINE_EX);
        }
        ret.append(LSConst.CMD_SPLIT_LINE_EX);
        return ret.toString();
    }

    /**
     * 将数据包转成字节数组,用于通过socket发送数据
     *
     * @return
     */
    public byte[] toBytes() {
        byte[] data;
        try {
            String paramAndProp = packageParamAndPropToString();
            data = paramAndProp.getBytes(LSConst.CHARSET_UTF8);
            byte[] tmp = getContentBytes();
            byte[] newData = new byte[data.length + tmp.length];
            System.arraycopy(data, 0, newData, 0, data.length);
            System.arraycopy(tmp, 0, newData, data.length, tmp.length);
            data = newData;
        } catch (UnsupportedEncodingException e) {
            data = packageParamAndPropToString().getBytes();
            e.printStackTrace();
        }
        //TODO:加密
        if (LSEncryptUtil.getInstance().getEncryptMode() == LSCmdHead.ENCRYPT_TYPE_AES256) {
            byte key1 = (byte) new Random().nextInt(255);
            byte key2 = (byte) new Random().nextInt(255);
            byte[] key = LSEncryptUtil.getInstance().getEncryptKey(key1, key2);
            try {
                data = LSAES256.encrypt(data, key, LSAES256.MODE_ENCRYPT);
            } catch (Exception e) {
                e.printStackTrace();
            }
            cmdHead.setEncryptKey1(key1);
            cmdHead.setEncryptKey2(key2);
            cmdHead.setEncryptType((byte) LSCmdHead.ENCRYPT_TYPE_AES256);
        } else if (LSEncryptUtil.getInstance().getEncryptMode() == LSCmdHead.ENCRYPT_TYPE_SM4) {
            byte key1 = (byte) new Random().nextInt(255);
            byte key2 = (byte) new Random().nextInt(255);
            byte[] key = LSEncryptUtil.getInstance().getEncryptKey(key1, key2);
            data = LSSM4.SM4(data, key, LSSM4.MODE_ENCRYPT);
            cmdHead.setEncryptKey1(key1);
            cmdHead.setEncryptKey2(key2);
            cmdHead.setEncryptType((byte) LSCmdHead.ENCRYPT_TYPE_SM4);
        }
        cmdHead.setCmdLength(LSConst.CMD_HEAD_LENGTH + data.length);

        LSDataBuffer buffer = new LSDataBuffer();
        buffer.write(cmdHead.getCmdHead());
        buffer.writeByteArray(data);
        return buffer.getBytes();
    }

    /**
     * 将数据包转成字符串形式,便于调试,打印指令数据log
     */
    @Override
    public String toString() {

        String data = packageParamAndPropToString();
        cmdHead.setCmdLength(data.length() + LSConst.CMD_HEAD_LENGTH + contentData.getLength());
        StringBuilder ret = new StringBuilder();
        ret.append(cmdHead.toString());
        ret.append(LSConst.CMD_SPLIT_LINE_EX);
        ret.append(data);
        ret.append(contentData.getDataString());
        return ret.toString();
    }
}
